from django.contrib.auth import authenticate, login, logout
from django.db.models import Sum
from django.http import HttpResponse, Http404
from django.shortcuts import render_to_response, redirect
from backend.models import Staff
from core.models import Level, Member, Address
from shop.models import Category, DessertItem
from shop.models import Sale, SaleEntry
from shop.models import WeekdayPlan, PlanEntry
from core.views import extract_date
import calendar
import datetime
import isoweek
import json

is_staff = lambda u: Staff.objects.filter(user=u).exists()

def staff_only(view_func):
    def _wrapper(request, *args, **kwargs):
        user = request.user
        if user.is_authenticated() and is_staff(user):
            return view_func(request, *args, **kwargs)
        return redirect('/backend/signin')

    return _wrapper

def signin(request):
    if request.method != 'POST':
        return render_to_response('backend/signin.html')

    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)

    if user and is_staff(user):
        login(request, user)
        return redirect('/backend')
    else:
        return render_to_response('backend/signin.html')

def signout(request):
    logout(request)
    return redirect('/backend')

@staff_only
def stat_member(request):
    return render_to_response('backend/stat-member.html')

@staff_only
def stat_sale(request):
    return render_to_response('backend/stat-sale.html')

@staff_only
def stat_product(request):
    return render_to_response('backend/stat-product.html')

# credit: http://stackoverflow.com/questions/765797/python-timedelta-in-years
def years_ago(years, from_date=None):
    if from_date is None:
        from_date = datetime.date.today()
    try:
        return from_date.replace(year=from_date.year - years)
    except:
        # Must be 2/29!
        # assert from_date.month == 2 and from_date.day == 29
        return from_date.replace(month=2, day=28, year=from_date.year-years)

def count_age_range(low, high):
    start_date = years_ago(high)
    end_date = years_ago(low)
    return Member.objects.filter(birthday__gte=start_date, birthday__lte=end_date).count()

def member_data(data):
    member_count = Member.objects.count()
    if data == 'member-all-names':
        p_names = map(lambda t: t[0], Member.objects.values_list('name'))
        return HttpResponse(json.dumps(p_names))
    elif data == 'member-active-expire':
        active_count = len([m for m in Member.objects.only('expire_date') if m.is_active()])
        expire_count = member_count - active_count
        items = [['Active', active_count], ['Expired', expire_count]]
        return HttpResponse(json.dumps(items))
    elif data == 'member-age':
        age_groups = (
            (0, 20),
            (20, 30),
            (30, 40),
            (40, 50),
            (50, 100),
        )
        items = [('%s - %s' % age_groups[i], count_age_range(*age_groups[i])) for i in range(len(age_groups))]
        items = filter(lambda item: item[1] != 0, items)
        return HttpResponse(json.dumps(items))

    elif data == 'member-gender':
        male_count = Member.objects.filter(gender='M').count()
        female_count = Member.objects.filter(gender='F').count()
        other_count = member_count - male_count - female_count
        items = [
            ['Male', male_count],
            ['Female', female_count],
            ['Other', other_count]
        ]
        return HttpResponse(json.dumps(items))
    elif data == 'member-location':
        states = set((a.state for a in Address.objects.only('state')))
        items = []
        for state in states:
            count = Address.objects.only('state').filter(state=state).count()
            items.append([state, count])
        return HttpResponse(json.dumps(items))
    elif data == 'member-level':
        levels = Level.objects.all()
        items = []
        for level in levels:
            count = Member.objects.filter(level=level).count()
            items.append(['%s'%level, count])
        return HttpResponse(json.dumps(items))

def sale_count_figure(start_time, end_time, is_reservation):
        sales = Sale.objects.filter(is_reservation=is_reservation, paid_time__gte=start_time, paid_time__lte=end_time)
        count = len(sales)
        figure = reduce(lambda x, y: x+y, map(lambda s: s.total(), sales), 0.0)
        return count, figure

def sale_stat(range_generator, is_reservation=False):
    labels = []
    counts = []
    figures = []
    for label, start, end in range_generator:
        labels.append(label)
        count, figure = sale_count_figure(start, end, is_reservation)
        counts.append(count)
        figures.append(figure)
    data = {
        'labels': labels,
        'counts': counts,
        'figures': figures,
    }
    return HttpResponse(json.dumps(data))

# returns a generator
def day_range(year, month, start_day, end_day):
    return ((
                d,
                datetime.datetime(year, month, d, 0, 0, 0),
                datetime.datetime(year, month, d, 23, 59, 59)
            )
        for d in range(start_day, end_day+1))
            
# returns a generator
def month_range(year, start_month, end_month):
    return ((
                datetime.date(year, m, 1).strftime('%B'),
                datetime.datetime(year, m, 1, 0, 0, 0),
                datetime.datetime(year, m, calendar.monthrange(year, m)[1], 23, 59, 59)
            )
        for m in range(start_month, end_month+1))
            

def sale_data(data):
    today = datetime.date.today()
    if data == 'sale-sale-daily':
        return sale_stat(day_range(today.year, today.month, 1, today.day))
    elif data == 'sale-reservation-daily':
        return sale_stat(day_range(today.year, today.month, 1, today.day), True)
    elif data == 'sale-sale-monthly':
        return sale_stat(month_range(today.year, 1, today.month))
    elif data == 'sale-reservation-monthly':
        return sale_stat(month_range(today.year, 1, today.month), True)

def top_products(hot=True, n=10, user=None):
    kwarg = {}
    if user:
        kwarg = {'sale__customer': user}
    res = DessertItem.objects.values_list('pk')
    pks = map(lambda t: t[0], res)
    qtys = map(lambda x: x['quantity__sum'] if x['quantity__sum'] else 0,
            map(lambda pk: SaleEntry.objects.filter(item__pk=pk, **kwarg).aggregate(Sum('quantity')), pks))
    _cmp = lambda p1, p2: p2[1] - p1[1]
    if not hot:
        _cmp = lambda p1, p2: p1[1] - p2[1]
    s_pairs = map(lambda p: (DessertItem.objects.get(pk=p[0]).name, p[1]), sorted(zip(pks, qtys), cmp=_cmp))[:n]
    return s_pairs

def top_category(hot=True, n=3, user=None):
    kwarg = {}
    if user:
        kwarg = {'sale__customer': user}
    res = Category.objects.values_list('pk')
    pks = map(lambda t: t[0], res)
    qtys = map(lambda x: x['quantity__sum'] if x['quantity__sum'] else 0,
            map(lambda pk: SaleEntry.objects.filter(item__category__pk=pk, **kwarg).aggregate(Sum('quantity')), pks))
    _cmp = lambda p1, p2: p2[1] - p1[1]
    if not hot:
        _cmp = lambda p1, p2: p1[1] - p2[1]
    s_pairs = map(lambda p: (Category.objects.get(pk=p[0]).name, p[1]), sorted(zip(pks, qtys), cmp=_cmp))[:n]
    return s_pairs

def product_data(data):
    if data == 'product-all-names':
        p_names = map(lambda t: t[0], DessertItem.objects.values_list('name'))
        return HttpResponse(json.dumps(p_names))
    elif data == 'product-category-sale-monthly':
        series = {}
        cates = Category.objects.all()
        for c in cates:
            series[c.name] = []

        labels = []
        today = datetime.date.today()
        for m in range(1, today.month+1):
            labels.append(datetime.date(today.year, m, 1).strftime('%B'))
            month_begin = datetime.datetime(today.year, m, 1, 0, 0, 0)
            month_end = datetime.datetime(today.year, m, calendar.monthrange(today.year, m)[1], 23, 59, 59)
            for c in cates:
                sales = Sale.objects.filter(paid_time__gte=month_begin, paid_time__lte=month_end, entries__item__category=c)
                series[c.name].append(reduce(lambda x, y: x+y, map(lambda s: s.total(), sales), 0.0))
        # generating data
        dic = {}
        dic['labels'] = labels
        dic['series'] = [{'name': name, 'data': data} for name, data in series.iteritems()]
        return HttpResponse(json.dumps(dic))
    elif data == 'product-category-sale-share':
        dic = []
        cates = Category.objects.all()
        for c in cates:
            sales = Sale.objects.filter(entries__item__category=c)
            figure = reduce(lambda x, y: x+y, map(lambda s: s.total(), sales), 0.0)
            dic.append([c.name, figure])

        return HttpResponse(json.dumps(dic))
    elif data == 'product-popular':
        pairs = top_products()
        data = {
            'labels': [p[0] for p in pairs],
            'series': [{
                'name': 'Sale Count',
                'data': [p[1] for p in pairs],
            }]
        }
        return HttpResponse(json.dumps(data))
    elif data == 'product-obscure':
        pairs = top_products(hot=False)
        data = {
            'labels': [p[0] for p in pairs],
            'series': [{
                'name': 'Sale Count',
                'data': [p[1] for p in pairs],
            }]
        }
        return HttpResponse(json.dumps(data))

def fetch_monthly_sales(name, is_product):
    today = datetime.date.today()
    labels = []
    series = []

    if is_product:
        p = DessertItem.objects.filter(name=name)[0]
        kwarg = {'entries__item': p}
    else:
        m = Member.objects.filter(name=name)[0]
        u = m.user
        kwarg = {'entries__sale__customer': u}
        
    for m in range(1, today.month+1):
        labels.append(datetime.date(today.year, m, 1).strftime('%B'))
        month_begin = datetime.datetime(today.year, m, 1, 0, 0, 0)
        month_end = datetime.datetime(today.year, m, calendar.monthrange(today.year, m)[1], 23, 59, 59)

        sales = Sale.objects.filter(paid_time__gte=month_begin, paid_time__lte=month_end, **kwarg)
        series.append(reduce(lambda x, y: x+y, map(lambda s: s.total(), sales), 0.0))

    return labels, series

def favorite_category_and_product(member_name):
    product = 'None'
    category = 'None'

    m = Member.objects.filter(name=member_name)[0]
    user = m.user
    product_sale = top_products(True, 1, user)[0]
    if product_sale[1]:
        product = product_sale[0]
    category_sale = top_category(True, 1, user)[0]
    if category_sale[1]:
        category = category_sale[0]

    return category, product

def analyze_well_being(series):
    last_month = 4000.00
    rise_or_drop = []

    # collect data
    for s in series:
        if s > last_month:
            rise_or_drop.append('+')
        elif s == last_month:
            rise_or_drop.append('=')
        else:
            rise_or_drop.append('-')
        s = last_month

    # use only the pattern of last three
    pattern = ''.join(rise_or_drop)[-3:]
    if pattern in ('---', '--=', '-=-', '=--', '=-='):
        level = 0
    elif pattern in ('==-', '+--', '+=-', '++-'):
        level = 1
    elif pattern in ('--+', '-=+', '-++', '=++', '==+'):
        level = 4
    elif pattern in ('-+-', '=-+', '=+-', '+-+'):
        level = 3
    elif pattern in ('+=+', '++=', '+++'):
        level = 5
    else:
        level = 2

    return level

level_string = ['crashing', 'declining', 'steady', 'fluctuating', 'rising', 'thriving']
suggestion_for_product = [
    'Consider take it off the shelf.',
    'Find out the problem!',
    'Do some compaigns to boost sale.',
    'Maybe lower the price somewhat?',
    'Your sale scheme is working.',
    'Stick to current plan!',
]
suggestion_for_member = [
    'Do something to save the member!',
    'Send a newsletter to promote sale.',
    'Find out what the customer like and give more.',
    'This is common. Don\'t worry.',
    'Keep the current scheme on.',
    'Pay special attention to this important customer.'
]

def pred_data(data, request):
    if data == 'pred-product':
        product_name = request.GET['product']

        labels, series = fetch_monthly_sales(product_name, is_product=True)

        # figure out suggestion
        level = analyze_well_being(series)

        # generating ouput
        dic = {}
        dic['labels'] = labels
        dic['series'] = [{'name': product_name, 'data': series}]
        dic['level'] = level
        dic['status'] = level_string[level]
        dic['suggestion'] = suggestion_for_product[level]
        return HttpResponse(json.dumps(dic))
    elif data == 'pred-member':
        member_name = request.GET['member']

        labels, series = fetch_monthly_sales(member_name, is_product=False)
        category, product = favorite_category_and_product(member_name)

        # figure out suggestion
        level = analyze_well_being(series)

        # generating ouput
        dic = {}
        dic['product'] = product
        dic['category'] = category
        dic['labels'] = labels
        dic['series'] = [{'name': member_name, 'data': series}]
        dic['level'] = level
        dic['status'] = level_string[level]
        dic['suggestion'] = suggestion_for_member[level]
        return HttpResponse(json.dumps(dic))

@staff_only
def get_data(request):
    if not request.is_ajax():
        return redirect('/backend')
    # note: this is an AJAX request, any exception would tell the client of failure
    # so no exception is handled here
    data = request.GET['data']
    if data.startswith('member'):
        return member_data(data)
    elif data.startswith('sale'):
        return sale_data(data)
    elif data.startswith('product'):
        return product_data(data)
    elif data.startswith('pred'):
        return pred_data(data, request)

@staff_only
def pred_product(request):
    return render_to_response('backend/pred-product.html')

@staff_only
def pred_member(request):
    return render_to_response('backend/pred-member.html')

@staff_only
def plan_add(request):
    if not request.is_ajax() or request.method != 'POST':
        return redirect('/backend/plan/daily')

    # check if plan exists
    # if not, create first
    date_str = request.POST['date']
    date = extract_date(date_str)
    if not date:
        raise Http404('Invalid Date String (valid format: yyyy-mm-dd)')
    item = DessertItem.objects.filter(name=request.POST['name'])
    if not item:
        raise Http404('Invalid Item Name')
    item = item[0]
    limit = int(request.POST['limit'])
    price = float(request.POST['price'])
    if int <= 0 or float <= 0.0:
        raise Http404('Invalid parameter')

    year, week, weekday  = date.isocalendar()
    plan = WeekdayPlan.objects.filter(year=year, week=week, weekday=weekday)
    if plan:
        plan = plan[0]
    else:
        plan = WeekdayPlan.objects.create(year=year, week=week, weekday=weekday)

    # check if entry to item exists
    # if not, create it
    if not PlanEntry.objects.filter(plan=plan, item=item).exists():
        staff = Staff.objects.get(user=request.user)
        PlanEntry.objects.create(creator=staff, plan=plan, item=item, price=price, limit=limit, stock=limit)
    # return updated table
    weekday_description = 'Weekday %s of Week %s, %s' % (weekday, week, year)
    params = {
        'plan': plan,
        'weekday_description': weekday_description,
    }
    return render_to_response('backend/weekday-plan.html', params)

@staff_only
def plan_weekly(request):
    if not request.is_ajax():
        return render_to_response('backend/plan-weekly.html')

    year = int(request.GET['year'])
    week_no = int(request.GET['week'])
    if (year <= 1 or year >= 9999) or \
    (week_no <= 0 or week_no > 52):
        raise Http404('Bad parameter')

    days = ['', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
    week = isoweek.Week(year, week_no)
    pairs = []
    for i in range(1, 8):
        day = days[i]
        date = getattr(week, day.lower())()
        description = '%s of Week %s, %s' % (day, week_no, year)
        res = WeekdayPlan.objects.filter(year=year, week=week_no, weekday=i)
        plan = res[0] if res else None
        pairs.append((description, plan))

    return render_to_response('backend/week-plan.html', {'pairs': pairs})

@staff_only
def plan_daily(request):
    if request.is_ajax():
        date_str = request.GET['date']
        date = extract_date(date_str)
        if not date:
            raise Http404('Invalid Date String (valid format: yyyy-mm-dd)')
        year, week, weekday  = date.isocalendar()
        plan = WeekdayPlan.objects.filter(year=year, week=week, weekday=weekday)
        if plan:
            plan = plan[0]

        weekday_description = 'Weekday %s of Week %s, %s' % (weekday, week, year)
        params = {
            'plan': plan,
            'weekday_description': weekday_description,
        }
        return render_to_response('backend/weekday-plan.html', params)

    return render_to_response('backend/plan-daily.html')
